import string
import threading

from irc import client
from irc.client import NickMask, ServerConnectionError

from kivy.logger import Logger

from amgineirc.view.settings.trustedView import EditTrustedPopup, TrustedView
from amgineirc.viewModel.channels.channelViewModel import ChannelViewModel
from amgineirc.viewModel.channels.sendReceiveViewModel import SendReceiveViewModel
from amgineirc.model.sortedDictList import SortedDictList

class ServerViewModel(SendReceiveViewModel):
    """
    """

    # channel commands
    desc_command = 'Topic'
    join_command = r'/join'

    def __init__(self, view, send_view, receive_view, server, appdata):
        SendReceiveViewModel.__init__(self,
                                      view,
                                      send_view,
                                      receive_view,
                                      None, # connection
                                      server, # target
                                      appdata)
        self._last_type = ''
        self._server_connection_runner = None
        self._trusted_timer = None
        self._channels = SortedDictList(string.punctuation)
        self._channels_loaded_started = False
        self._channels_loaded_finished = False
        self._trusted = []
        
    def _build_ignore_list(self):
        string.letters + string.digits

    def handle_on_remove(self):
        self.handle_view_send('/quit' + self._target)
        SendReceiveViewModel.handle_on_remove(self)
        if self.connected:
            self._connection.disconnect()

    # tab header commands for motd, channels, trusted users

    # motd

    def th_display_motd(self):
        # called by the tab header view model
        pass
        
    # channels - a 3 step process
    
    def th_display_channels(self, *unused_largs):
        """called by the tab header view model.
        allow the user to type the name of a channel.
        the autocomplete lets the user select a channel name while typing.
        """
        if self._channels_loaded_started:
            # the channel list is at least partially loaded
            if self._channels_loaded_finished:
                # all channels have finished loading
                self.view.message_edit_line('Channel',
                                            'Enter a channel name.',
                                            '', # default text
                                            self.w_handle_channel,
                                            autocomplete_dict=self._channels)
            else:
                self.view.message_warn('The channels are still being loaded from the server. Try again in a few seconds.')
        else:
            # channels have not started loading
            # they may never start
            # allow the user to enter a channel name without auto complete
            self.view.message_edit_line('Channel',
                                        'Enter a channel name.',
                                        '', # default text
                                        self.w_handle_channel)

    def w_handle_channel(self, channel, *largs):
        # the user has selected a channel
        # display commands for that channel
        if channel in self._channels:
            topic = self._channels[channel]
        else:
            topic = ''
        self.view.select_options(channel + ' Channel',
                                 [ServerViewModel.join_command],
                                 self.w_handle_channel_command,
                                 channel,
                                 message=topic, # topic
                                 back_handler=self.th_display_channels)

    def w_handle_channel_command(self, command, *largs):
        # perform a command for with channel
        channel = largs[0]
        if command == ServerViewModel.join_command:
            # join a channel
            self._appdata['connection_vm'].add_channel_connection(self._connection,
                                                                  channel)        

    # trusted ison
    
    def _setup_next_ison(self):
        self._trusted_timer = threading.Timer(30.0, self.check_trusted_ison)
        self._trusted_timer.start()

    def check_trusted_ison(self):
        if self.connected:
            try:
                nicks = self._appdata['trusted_users'].nick_list(self.server)
                if len(nicks) > 0:
                    self._connection.ison(nicks)
                else:
                    self._trusted_timer = None
            except:
                self._trusted_timer = None

    def set_nick_trusted(self, nick, is_trusted):
        if is_trusted:
            # make sure the
            self._setup_next_ison() 
        else:
            # not trusted
            if nick in self._trusted:
                self._trusted.remove(nick)

    # trusted - a three step process

    def th_show_trusted(self, *unused_largs):
        # called by the tab header view model
        # display the trusted user nicks
        self.view.select_options('Trusted Users',
                                 self._trusted,
                                 self.w_handle_trusted_nick)
        
    def w_handle_trusted_nick(self, nick, *largs):
        # display commands for a nick
        if len(nick > 0):
            if nick[0] == '@':
                clean_nick = nick[1:]
            else:
                clean_nick = nick
            self.view.select_options('Commands for ' + nick,
                                     [ChannelViewModel.dcc_command, ChannelViewModel.prvmsg_command, ChannelViewModel.untrust_command],
                                     self.w_handle_trusted_command,
                                     (clean_nick),
                                     back_handler=self.th_show_trusted)

    def w_handle_trusted_command(self, command, *largs):
        # perform a command with a nick
        target = largs[0]
        # a command from a button
        if command == ChannelViewModel.dcc_command:
            # make sure that the settings folder is available
            if not self._appdata['servers'].have_configuration_folder():
                warning = self._appdata['servers'].missing_configuration_folder_message
                self.view.message_warn(warning)
            else:
                # open a view for this new connection
                # let its vm start the listening
                if self.connected:
                    self._appdata['client'].send_dcc_chat_request(self._connection, target)
        elif command == ChannelViewModel.untrust_command:
            # let the trusted view model handle it
            self._appdata['trusted_vm'].handle_option_command(TrustedView.delete_command, target, self.server)
        else:
            # privmsg command
            # make sure that the settings folder is available
            if not self._appdata['servers'].have_configuration_folder():
                warning = self._appdata['servers'].missing_configuration_folder_message
                self.view.message_warn(warning)
            else:
                # send the privmsg command to the server
                if self.connected:
                    SendReceiveViewModel.w_handle_command(self, command, target)

    # recieve view methods
    
    def handle_client_event(self, evtarget, evtype, evserver, evsource, evargs):
        """
        handle an event from the client
        store event and start the logger to the view
        """
        # the target is the user or channel
        # so create the view target
        # which when the user sends is what this user is sending to
        # but in this case is the source.
        if '!' in evsource and '@' in evsource:
            # the view target is the source
            nmask = NickMask(evsource)
            source = nmask.nick
        else:
            source = evsource
        # these get sent to the correct channel
        if (evtype == 'privmsg' and source != self._target) \
        or (evtype == 'privnotice' and source == 'NickServ'):
            message = ' '.join(evargs)
            self._appdata['connection_vm'].add_privmsg_connection(self._connection,
                                                                  source,
                                                                  received_message=message)
        elif evtype == 'quit':
            # a user on the server quit so tell all the channel views
            # evsource is the nick
            self._appdata['connection_vm'].update_user_lists_quit(source, self.server)
        elif evtype == 'topicinfo'\
            or evtype == 'currenttopic'\
            or evtype == 'namreply'\
            or evtype == 'endofnames':
            message = ' '.join(evargs)
            if message[:2] == '= ':
                message = message[2:]
            args = message.split(None, 1)
            if len(args) == 2:
                # resend data
                # the target is a channel (evtarget)
                # the source may be a user in the channel
                # on some servers the target (channel) 'Libertytreeradio' is like 'libertytreeradio'
                # so use lower on the target (channel)
                self._appdata['connection_vm'].resend_client_event(evserver,
                                                                   args[0].lower(),
                                                                   evtype,
                                                                   evsource,
                                                                   [args[1]])
        elif evtype == 'ison':
            self._trusted = ' '.join(evargs).split()
            self._trusted.sort()
            self._setup_next_ison()
            return
        elif evtype == 'liststart'\
            or evtype == 'ping':
            # ignore
            pass
        elif evtype == 'nick':
            # send this to all channel views
            nmask = NickMask(evsource)
            old_nick = nmask.nick
            Logger.debug('ServerViewModel.handle_client_event: nick %s, %s' % (old_nick, evtarget))
            self._appdata['connection_vm'].update_nick_change(self.server, old_nick, evtarget)
        else:
            # these go to the server view's log view
            if evtype == 'motd' or evtype == 'endofmotd':
                # continue from the last log entry
                message = ' '.join(evargs)
            elif evtype == '378'\
                 or evtype == 'whoischannels'\
                 or evtype == 'whoisserver'\
                 or evtype == 'whoisidle'\
                 or evtype == 'endofwhois':
                # these follow 'whoisuser' so keep them together
                message = ' '.join(evargs)
            elif evtype == 'list':
                self._channels_loaded_started = True
                # the list reply (channels)
                message = ' '.join(evargs)
                parts = message.split(None, 3)
                if len(parts) == 4:
                    topic = parts[3]
                else:
                    topic = ''
                # store the channel and topic
                self._channels[parts[0]] = topic
                return
            elif evtype == 'listend':
                # end of channels list
                self._channels_loaded_finished = True
                return
            elif evtype == 'exception':
                message = self._format_new_log_entry(evtype, evtarget, evsource, evargs)
            elif evtype == 'input':
                # a command that the user typed
                message = ' '.join(evargs)
            else:
                # any other event type goes to the view
                if evtype == self._last_type:
                    # continue from the last log entry
                    message = ' '.join(evargs)
                else:
                    # start a new log entry
                    # format the message with type, target, source
                    message = self._format_new_log_entry(evtype, evtarget, evsource, evargs)
            # add the message
            self._receive_view.vm_log(message)
            self._last_type = evtype
            # show the page as updated
            if self._appdata['connection_vm'] is not None:
                self._appdata['connection_vm'].page_updated(self.server, self.target)

    # send view methods

    def handle_view_send(self, message):
        """ the user has entered text to send.
        send the message over the network.
        """
        if self._connection is None:
            return
        try:
            m = message.strip()
            if m[0] == '/' and self._handle_view_send_privmsg(m):
                return
            elif m[:5] == '/ison':
                # ignore
                return
            else:
                if m[0] != '/':
                    message = 'Unknown IRC command.'
                else:
                    if not SendReceiveViewModel.handle_view_send(self, message):
                        message = 'Unknown IRC command.'
                self.handle_client_event(self._target,
                                         'input',
                                         self.server,
                                         self._nick,
                                         [message])
        except Exception as e:
            message = repr(e)
            self.handle_client_event(self._target,
                                     'exception',
                                     self.server,
                                     '',
                                     [message])

    # client methods
    
    def handle_on_connect(self, connection):
        # update the views
        SendReceiveViewModel.handle_on_connect(self, connection)
        message = 'You are now connected to %s.' % (self.server)
        self.handle_client_event(self.target, 'connected', self.server, self.server, [message])
        # reset server uri to the real server uri
        # irc client model already reset this vm for the new server uri
        t1 = threading.Timer(5.0, self.send_initial_commands)
        t1.start()

    def handle_on_disconnect(self):
        if self._trusted_timer is not None:
            self._trusted_timer.cancel()
        self._last_type = ''
        self._channels = SortedDictList(string.punctuation)
        self._channels_loaded_started = False
        self._channels_loaded_finished = False
        self._trusted = []
        message = 'You are now disconnected from %s.' % (self.server)
        self.handle_client_event(self.target, 'disconnected', self.server, self.server, [message])
        SendReceiveViewModel.handle_on_disconnect(self)

    def send_initial_commands(self):
        try:
            # send the list command for the channels
            self._connection.list([], '')
        except:
            pass
        # check for trusted nicks ison
        self.check_trusted_ison()

    def connect(self):
        # connect using a thread
        self._server_connection_runner = threading.Thread(group=None,
                                                          target=self.run_server_connection,
                                                          name='ServerConnectionRunner')
        self._server_connection_runner.start()

    def run_server_connection(self):
        # connect to the server
        client = self._appdata['client']
        message = None
        record = self._appdata['servers'].data[self._target]
        try:
            self._connection = client.server_connect(self._target, record['port'], record['secure'], record['nick'])
            self.handle_on_connect(self._connection)
        except ServerConnectionError as x:
            self._connection = None
            self.handle_client_event(self._target,
                                     'exception',
                                     'amgineirc',
                                     self._target,
                                     [repr(x)])

    # private methods
